﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Orleans;
using Orleans.Configuration;
using Orleans.Hosting;
using System;

namespace IOP.Orleans.Server
{
    /// <summary>
    /// 奥尔良主机扩展
    /// </summary>
    public static class OrleansSiloExtensions
    {
        /// <summary>
        /// 构建奥尔良服务主机
        /// </summary>
        /// <param name="hostBuilder">主机构建者</param>
        /// <param name="optionName">配置项在主机配置文件中的位置</param>
        /// <param name="siloAction">奥尔良构建委托</param>
        /// <returns></returns>
        public static IHostBuilder CreateOrleansServicesServer(this IHostBuilder hostBuilder, string optionName, 
            Action<Microsoft.Extensions.Hosting.HostBuilderContext, ISiloHostBuilder> siloAction)
        {
            ServerHostOptions options = null;
            hostBuilder.ConfigureServices((context, services) =>
            {
                services.AddOptions();
                services.Configure<ServerHostOptions>(context.Configuration.GetSection(optionName));
                var provider = services.BuildServiceProvider();
                var o = provider.GetRequiredService<IOptions<ServerHostOptions>>();
                options = o.Value;
                ISiloHostBuilder siloHostBuilder = new SiloHostBuilder();
                siloHostBuilder.ConfigureOrleans(context, options, siloAction);
                ISiloHost siloHost = siloHostBuilder.Build();
                services.AddSingleton(siloHost);
                services.AddHostedService<OrleansServerMonitorHostService>();
            });   
            return hostBuilder;
        }

        /// <summary>
        /// 构建奥尔良服务主机
        /// </summary>
        /// <param name="hostBuilder"></param>
        /// <param name="options"></param>
        /// <param name="siloAction"></param>
        /// <returns></returns>
        public static IHostBuilder CreateOrleansServicesServer(this IHostBuilder hostBuilder, Action<ServerHostOptions> options, Action<Microsoft.Extensions.Hosting.HostBuilderContext, ISiloHostBuilder> siloAction)
        {
            if (options == null) throw new ArgumentNullException(nameof(options), "the ServerHostOptions Action is null");
            ServerHostOptions o = new ServerHostOptions();
            options.Invoke(o);
            hostBuilder.ConfigureServices((context, services) =>
            {
                ISiloHostBuilder siloHostBuilder = new SiloHostBuilder();
                siloHostBuilder.ConfigureOrleans(context, o, siloAction);
                ISiloHost siloHost = siloHostBuilder.Build();
                services.AddSingleton(siloHost);
                services.AddHostedService<OrleansServerMonitorHostService>();
                services.AddHostedService<OrleansServerMonitorHostService>();
            });
            return hostBuilder;
        }

        /// <summary>
        /// 配置奥尔良
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="options"></param>
        /// <param name="siloAction"></param>
        private static void ConfigureOrleans(this ISiloHostBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext hostBuilderContex, ServerHostOptions options, Action<Microsoft.Extensions.Hosting.HostBuilderContext, ISiloHostBuilder> siloAction)
        {
            if (options == null) throw new ArgumentNullException(nameof(ServerHostOptions), "the ServerHostOptions is null");
            if (options.Cluster == null) throw new ArgumentNullException(nameof(options.Cluster), $"The options {nameof(ClusterOptions)} is necessary in orlean server, please add options {nameof(ClusterOptions)} in node configure");
            builder.Configure<ClusterOptions>(o =>
            {
                o.ClusterId = options.Cluster.ClusterId;
                o.ServiceId = options.Cluster.ServiceId;
            });
            if (string.IsNullOrEmpty(options.ClusterProvider)) options.ClusterProvider = ClusteringScheme.Custom;
            switch (options.ClusterProvider)
            {
                case ClusteringScheme.AdoNet:
                    if (options.AdoNetClustering == null) throw new ArgumentNullException(nameof(options.AdoNetClustering), $"Cannot found AdoNetClustering option in ServerHostOptions");
                    builder.UseAdoNetClustering(o =>
                    {
                        o.ConnectionString = options.AdoNetClustering.ConnectionString;
                        o.Invariant = options.AdoNetClustering.Invariant;
                    });
                    break;
                case ClusteringScheme.Local:
                    builder.UseLocalhostClustering();
                    break;
                case ClusteringScheme.Custom:
                    break;
                default:
                    throw new Exception($"Unknow clusterProvider with {options.ClusterProvider}");
            }
            if (options.Endpoint != null) builder.ConfigureEndpoints(options.Endpoint.SiloPort, options.Endpoint.GatewayPort);
            siloAction?.Invoke(hostBuilderContex, builder);
        }
    }
}
